///PHILOTES Source Code.  (C)2012 PhiloLabs
#pragma once

#include "ExtrasPrerequisites.h"
#include "OgreResource.h"
#include "OgreTexture.h"
#include "OgreMaterial.h"
#include "OgreCommon.h"

namespace Extras
{

    enum FontType
    {
        /// Generated from a truetype (.ttf) font
        FT_TRUETYPE = 1,
        /// Loaded from an image created by an artist
        FT_IMAGE = 2
    };


	class _PhiloExtrasExport Font : public Ogre::Resource, public Ogre::ManualResourceLoader
    {
    protected:
		/// Command object for Font - see ParamCommand 
		class _PhiloExtrasExport CmdType : public Ogre::ParamCommand
		{
		public:
			Ogre::String doGet(const void* target) const;
			void doSet(void* target, const Ogre::String& val);
		};
		/// Command object for Font - see ParamCommand 
		class _PhiloExtrasExport CmdSource : public Ogre::ParamCommand
		{
		public:
			Ogre::String doGet(const void* target) const;
			void doSet(void* target, const Ogre::String& val);
		};
		/// Command object for Font - see ParamCommand 
		class _PhiloExtrasExport CmdSize : public Ogre::ParamCommand
		{
		public:
			Ogre::String doGet(const void* target) const;
			void doSet(void* target, const Ogre::String& val);
		};
		/// Command object for Font - see ParamCommand 
		class _PhiloExtrasExport CmdResolution : public Ogre::ParamCommand
		{
		public:
			Ogre::String doGet(const void* target) const;
			void doSet(void* target, const Ogre::String& val);
		};
		/// Command object for Font - see ParamCommand 
		class _PhiloExtrasExport CmdCodePoints : public Ogre::ParamCommand
		{
		public:
			Ogre::String doGet(const void* target) const;
			void doSet(void* target, const Ogre::String& val);
		};

		// Command object for setting / getting parameters
		static CmdType msTypeCmd;
		static CmdSource msSourceCmd;
		static CmdSize msSizeCmd;
		static CmdResolution msResolutionCmd;
		static CmdCodePoints msCodePointsCmd;

		/// The type of font
        FontType mType;

        /// Source of the font (either an image name or a truetype font)
        Ogre::String mSource;

        /// Size of the truetype font, in points
        Real mTtfSize;
        /// Resolution (dpi) of truetype font
        uint mTtfResolution;
		/// Max distance to baseline of this (truetype) font
		int mTtfMaxBearingY;


	public:
		typedef uint32 CodePoint;
		typedef Ogre::FloatRect UVRect;
		/// Information about the position and size of a glyph in a texture
		struct GlyphInfo 
		{
			CodePoint codePoint;
			UVRect uvRect;
			Real aspectRatio;

			GlyphInfo(CodePoint id, const UVRect& rect, Real aspect)
				: codePoint(id), uvRect(rect), aspectRatio(aspect)
			{

			}
		};
		/// A range of code points, inclusive on both ends
		typedef std::pair<CodePoint, CodePoint> CodePointRange;
		typedef Ogre::vector<CodePointRange>::type CodePointRangeList;
	protected:
		/// Map from unicode code point to texture coordinates
		typedef map<CodePoint, GlyphInfo>::type CodePointMap;
		CodePointMap mCodePointMap;

        /// The material which is generated for this font
        MaterialPtr mMaterial;

		/// Texture pointer
		TexturePtr mTexture;

        /// for TRUE_TYPE font only
        bool mAntialiasColour;

		/// Range of code points to generate glyphs for (truetype only)
		CodePointRangeList mCodePointRangeList;

        /// Internal method for loading from ttf
        void createTextureFromFont(void);

		/// @copydoc Resource::loadImpl
		virtual void loadImpl();
		/// @copydoc Resource::unloadImpl
		virtual void unloadImpl();
		/// @copydoc Resource::calculateSize
		size_t calculateSize(void) const { return 0; } // permanent resource is in the texture 
    public:

        
		Font(ResourceManager* creator, const Ogre::String& name, ResourceHandle handle,
			const Ogre::String& group, bool isManual = false, ManualResourceLoader* loader = 0);
        virtual ~Font();

        
        void setType(FontType ftype);

        
        FontType getType(void) const;

        
        void setSource(const Ogre::String& source);

        
        const Ogre::String& getSource(void) const;

        
        void setTrueTypeSize(Real ttfSize);
        
        void setTrueTypeResolution(uint ttfResolution);

        
        Real getTrueTypeSize(void) const;
        
        uint getTrueTypeResolution(void) const;
		
		int getTrueTypeMaxBearingY() const;


        
        inline const UVRect& getGlyphTexCoords(CodePoint id) const
        {
			CodePointMap::const_iterator i = mCodePointMap.find(id);
			if (i != mCodePointMap.end())
			{
				return i->second.uvRect;
			}
			else
			{
				static UVRect nullRect(0.0, 0.0, 0.0, 0.0);
				return nullRect;
			}
        }

        
        inline void setGlyphTexCoords(CodePoint id, Real u1, Real v1, Real u2, Real v2, Real textureAspect)
        {
			CodePointMap::iterator i = mCodePointMap.find(id);
			if (i != mCodePointMap.end())
			{
				i->second.uvRect.left = u1;
				i->second.uvRect.top = v1;
				i->second.uvRect.right = u2;
				i->second.uvRect.bottom = v2;
				i->second.aspectRatio = textureAspect * (u2 - u1)  / (v2 - v1);
			}
			else
			{
				mCodePointMap.insert(
					CodePointMap::value_type(id, 
						GlyphInfo(id, UVRect(u1, v1, u2, v2), 
							textureAspect * (u2 - u1)  / (v2 - v1))));
			}

        }
        
        inline Real getGlyphAspectRatio(CodePoint id) const
        {
			CodePointMap::const_iterator i = mCodePointMap.find(id);
			if (i != mCodePointMap.end())
			{
				return i->second.aspectRatio;
			}
			else
			{
				return 1.0;
			}
        }
        
        inline void setGlyphAspectRatio(CodePoint id, Real ratio)
        {
			CodePointMap::iterator i = mCodePointMap.find(id);
			if (i != mCodePointMap.end())
			{
				i->second.aspectRatio = ratio;
			}
        }

		
		const GlyphInfo& getGlyphInfo(CodePoint id) const;

		
		void addCodePointRange(const CodePointRange& range)
		{
			mCodePointRangeList.push_back(range);
		}

		
		void clearCodePointRanges()
		{
			mCodePointRangeList.clear();
		}
		
		const CodePointRangeList& getCodePointRangeList() const
		{
			return mCodePointRangeList;
		}
        
        inline const MaterialPtr& getMaterial() const
        {
            return mMaterial;
        }
        
        inline const MaterialPtr& getMaterial()
        {
            return mMaterial;
        }
        
        inline void setAntialiasColour(bool enabled)
        {
        	mAntialiasColour = enabled;
        }

		
        inline bool getAntialiasColour(void) const
        {
            return mAntialiasColour;
        }

		
		void loadResource(Resource* resource);
    };
	
	class _PhiloExtrasExport FontPtr : public SharedPtr<Font> 
	{
	public:
		FontPtr() : SharedPtr<Font>() {}
		explicit FontPtr(Font* rep) : SharedPtr<Font>(rep) {}
		FontPtr(const FontPtr& r) : SharedPtr<Font>(r) {} 
		FontPtr(const ResourcePtr& r) : SharedPtr<Font>()
		{
			// lock & copy other mutex pointer
            OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
            {
			    OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
			    OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
			    pRep = static_cast<Font*>(r.getPointer());
			    pUseCount = r.useCountPointer();
			    if (pUseCount)
			    {
				    ++(*pUseCount);
			    }
            }
		}

		/// Operator used to convert a ResourcePtr to a FontPtr
		FontPtr& operator=(const ResourcePtr& r)
		{
			if (pRep == static_cast<Font*>(r.getPointer()))
				return *this;
			release();
			// lock & copy other mutex pointer
            OGRE_MUTEX_CONDITIONAL(r.OGRE_AUTO_MUTEX_NAME)
            {
                OGRE_LOCK_MUTEX(*r.OGRE_AUTO_MUTEX_NAME)
			    OGRE_COPY_AUTO_SHARED_MUTEX(r.OGRE_AUTO_MUTEX_NAME)
			    pRep = static_cast<Font*>(r.getPointer());
			    pUseCount = r.useCountPointer();
			    if (pUseCount)
			    {
				    ++(*pUseCount);
			    }
            }
			else
			{
				// RHS must be a null pointer
				assert(r.isNull() && "RHS must be null if it has no mutex!");
				setNull();
			}
			return *this;
		}
	};
		
}
